////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
//                                                                                                                            //
//                                                         WNProject                                                          //
//                                                                                                                            //
//         This file is distributed under the BSD 2-Clause open source license. See Licenses/License.txt for details.         //
//                                                                                                                            //
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include "WNScripting/inc/WNCodeModule.h"
#include "WNScripting/inc/WNScriptingMemoryManager.h"
#include "WNScripting/inc/WNParameter.h"
#include "WNScripting/inc/WNTypeManager.h"

#ifdef _WN_MSVC
    #pragma warning(push)
    #pragma warning(disable: 4100)
    #pragma warning(disable: 4127)
    #pragma warning(disable: 4152)
    #pragma warning(disable: 4244)
    #pragma warning(disable: 4245)
    #pragma warning(disable: 4267)
    #pragma warning(disable: 4355)
    #pragma warning(disable: 4512)
#endif

#include "llvm/ExecutionEngine/MCJIT.h"
#include "llvm/IR/Module.h"
#include "llvm/IR/LLVMContext.h"
#include "llvm/ADT/Triple.h"
#include "llvm/IR/IRBuilder.h"

#ifdef _WN_MSVC
    #pragma warning(pop)
#endif

using namespace WNScripting;

WNCodeModule::WNCodeModule(WNTypeManager& _manager, WNScopedVariableList& _variableList, WNScriptingEngine* _scriptingEngine) :
    mTypeManager(_manager),
    mScopedVariableList(_variableList),
    mScriptingEngine(_scriptingEngine) {
}

const WNScriptingEngine* WNCodeModule::GetScriptingEngine() const {
    return(mScriptingEngine);
}

WNTypeManager& WNCodeModule::GetTypeManager() const {
    return(mTypeManager);
}

WNScopedVariableList& WNCodeModule::GetScopedVariableList() {
    return(mScopedVariableList);
}

eWNTypeError WNCodeModule::Initialize(WN_UINT32 flags, WNScriptingMemoryManager& _manager) {
    mModule = WN_NEW llvm::Module("MCJitModule", llvm::getGlobalContext());

#ifdef _WN_ANDROID
#ifdef _WN_ARM
    mModule->setTargetTriple(llvm::Triple::normalize("arm-linux-androideabi"));
#else
    mModule->setTargetTriple(llvm::Triple::normalize("x86-linux-androideabi"));
#endif
#elif defined(_WN_WINDOWS)
    #ifdef _WN_X64
        mModule->setTargetTriple(llvm::Triple::normalize("x86_64-pc-win32-elf"));
    #else
        mModule->setTargetTriple(llvm::Triple::normalize("i686-pc-win32-elf"));
    #endif
#endif
    llvm::EngineBuilder  builder(mModule);
    builder.setEngineKind(llvm::EngineKind::JIT);
    builder.setUseMCJIT(true);
    builder.setJITMemoryManager(&_manager);

    if(flags & eOptimized ) {
        builder.setOptLevel(llvm::CodeGenOpt::Aggressive);
    } else {
        builder.setOptLevel(llvm::CodeGenOpt::None);
    }
    mEngine = builder.create();
    if(!mEngine) {
        delete(mModule);
        return(eWNError);
    }
    mBuilder = WN_NEW llvm::IRBuilder<>(llvm::getGlobalContext());
    if(!mBuilder) {
         WN_DELETE(mEngine);
         WN_DELETE(mModule);
         return(eWNError);
    }

    return(eWNOK);
}

llvm::ExecutionEngine* WNCodeModule::GetExecutionEngine() {
    return(mEngine);
}

llvm::Module* WNCodeModule::GetModule() {
    return(mModule);
}

llvm::IRBuilderBase* WNCodeModule::GetBuilder() {
    return(mBuilder);
}

WNFunctionDefinition* WNCodeModule::GetFunctionDefinition(const WN_CHAR* _name, const std::vector<WNParameter>& _params) {
    for(std::vector<WNFunctionDefinition*>::iterator i = mFunctions.begin(); i != mFunctions.end(); ++i) {
        if(WNStrings::WNStrCmp((*i)->mName, _name) == 0) {
            
            if(_params.size() != (*i)->mTypes.size()) {
                continue;
            }
            size_t j = 0;
            for(; j < _params.size(); ++j) {
                if(_params[j].mType != (*i)->mTypes[j]) {
                    break;
                }
            }
            if (j == _params.size()) {
                return(*i);
            }
        }
    }
    return(WN_NULL);
}

WNFunctionDefinition* WNCodeModule::GetFunctionDefinition(const WN_CHAR* _name, const std::vector<WNScriptType>& _params) {
    for(std::vector<WNFunctionDefinition*>::iterator i = mFunctions.begin(); i != mFunctions.end(); ++i) {
        if(WNStrings::WNStrCmp((*i)->mName, _name) == 0) {
            
            if(_params.size() != (*i)->mTypes.size()) {
                continue;
            }
            size_t j = 0;
            for(; j < _params.size(); ++j) {
                if(_params[j] != (*i)->mTypes[j]) {
                    break;
                }
            }
            if (j == _params.size()) {
                return(*i);
            }
        }
    }
    return(WN_NULL);
}

eWNTypeError WNCodeModule::GetCastableFunctionDefinition(const WN_CHAR* _name, const std::vector<WNScriptType>& _params, WNFunctionDefinition*& _outDefinition) {
    std::vector<WNFunctionDefinition*> sharedNameDefs;
    for(std::vector<WNFunctionDefinition*>::iterator i = mFunctions.begin(); i != mFunctions.end(); ++i) {
        if(WNStrings::WNStrCmp((*i)->mName, _name) == 0) {
            if((*i)->mTypes.size() == _params.size()) {
                sharedNameDefs.push_back(*i);
            }
        }
    }
    for(size_t i = 0; i < _params.size(); ++i) {
        for(std::vector<WNFunctionDefinition*>::iterator vIt =  sharedNameDefs.begin(); vIt != sharedNameDefs.end(); ) {
            if( (*vIt)->mTypes[i] != _params[i]) {
                if(!mTypeManager.GetCastingOperation(_params[i], (*vIt)->mTypes[i])) {
                    vIt = sharedNameDefs.erase(vIt);
                    continue;
                }
                vIt++;
            }
            vIt++;
        }
    }

    if(sharedNameDefs.empty()) {
        return(eWNError);
    }
    if(sharedNameDefs.size() > 1) {
        return(eWNAmbiguous);
    }
    _outDefinition = *sharedNameDefs.begin();
    return(eWNOK);
}

eWNTypeError WNCodeModule::GenerateFunctionDefinition(const WN_CHAR* _name, const std::vector<WNScriptType>& _params, const WNScriptType& _return, WNFunctionDefinition*& _outFunctionDefinition) {
    _outFunctionDefinition = GetFunctionDefinition(_name, _params);
    if(_outFunctionDefinition){
        return(eWNAlreadyExists);
    }
    WNFunctionDefinition* def = WN_NEW WNFunctionDefinition();
    def->mName = WNStrings::WNStrNDup(_name, 256);
    def->mReturn = _return;
    def->mTypes.assign(_params.begin(), _params.end());
    def->mFunction = WN_NULL;
    def->mFunctionType = WN_NULL;
    def->mFunctionPointer = WN_NULL;
    _outFunctionDefinition = def;
    return(eWNOK);
}

eWNTypeError WNCodeModule::AddFunctionDefinition(const WN_CHAR* _name, const std::vector<WNScriptType>& _params, const WNScriptType& _return, WNFunctionDefinition*& _outFunctionDefinition) {
    eWNTypeError err = GenerateFunctionDefinition(_name, _params, _return, _outFunctionDefinition);
    if(eWNOK != err) {
        return(err);
    }
    mFunctions.push_back(_outFunctionDefinition);
    return(eWNOK);
}

eWNTypeError WNCodeModule::AddExternalScriptFunction(WNFunctionDefinition* _functionDefinition) {
    mFunctions.push_back(_functionDefinition);
    return(eWNOK);
}

bool WNCodeModule::NamedFunctionExists(const WN_CHAR* _name) const {
    for(std::vector<WNFunctionDefinition*>::const_iterator i = mFunctions.begin(); i != mFunctions.end(); ++i) {
        if(WNStrings::WNStrCmp((*i)->mName, _name) == 0) {
            return(true);
        }
    }
    return(false);
}

eWNTypeError WNCodeModule::AddExternalDefinition(const WN_CHAR* _name, const WN_CHAR* _tag, const std::vector<WNScriptType>& _params, const WNScriptType& _return) {
    if(GetFunctionDefinition(_name, _params)) {
        return(eWNAlreadyExists);
    }

    WNFunctionDefinition* def = WN_NEW WNFunctionDefinition();
    def->mName = WNStrings::WNStrNDup(_name, 256);
    def->mTag = WNStrings::WNStrNDup(_tag, 1024);
    def->mReturn = _return;
    def->mTypes.assign(_params.begin(), _params.end());

    std::vector<llvm::Type*> scriptTypes;
    for(std::vector<WNScriptType>::const_iterator i = _params.begin(); i != _params.end(); ++i) {
        scriptTypes.push_back((*i)->mLLVMType);
    }

    llvm::FunctionType* fTy = llvm::FunctionType::get(_return->mLLVMType, scriptTypes, false);
    llvm::Function* func = llvm::Function::Create(fTy, llvm::GlobalValue::ExternalLinkage, def->mTag, mModule);
    def->mFunctionType = fTy;
    def->mFunction = func;
    mFunctions.push_back(def);
    return(eWNOK);
}

